/* 将分解的时间戳转换为字符串。  */

/*
 * Copyright 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *	  may be used to endorse or promote products derived from this software
 *	  without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * 基于 UCB 版本，版权声明见上文。
 *
 * 当 "多字节字符 == 普通字符" 时，这符合 ANSI 标准。
 *
 * 识别信息
 *	  src/timezone/strftime.c
 */

#include "postgres.h"

#include <fcntl.h>

#include "private.h"


struct lc_time_T
{
	const char *mon[MONSPERYEAR];
	const char *month[MONSPERYEAR];
	const char *wday[DAYSPERWEEK];
	const char *weekday[DAYSPERWEEK];
	const char *X_fmt;
	const char *x_fmt;
	const char *c_fmt;
	const char *am;
	const char *pm;
	const char *date_fmt;
};

#define Locale	(&C_time_locale)

static const struct lc_time_T C_time_locale = {
	{
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	}, {
		"January", "February", "March", "April", "May", "June",
		"July", "August", "September", "October", "November", "December"
	}, {
		"Sun", "Mon", "Tue", "Wed",
		"Thu", "Fri", "Sat"
	}, {
		"Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday"
	},

	/* X_fmt */
	"%H:%M:%S",

	/*
	 * x_fmt
	 *
	 * C99 及更高版本要求此格式。像这里一样仅使用数字使
	 * 桂克尔更开心；它也是与 SVR4 兼容的。
	 */
	"%m/%d/%y",

	/*
	 * c_fmt
	 *
	 * C99 及更高版本要求此格式。之前此代码使用 "%D %X"，但
	 * 我们现在遵循 C99。请注意，"%a %b %d %H:%M:%S %Y" 是
	 * Solaris 2.3 使用的格式。
	 */
	"%a %b %e %T %Y",

	/* am */
	"AM",

	/* pm */
	"PM",

	/* date_fmt */
	"%a %b %e %H:%M:%S %Z %Y"
};

enum warn
{
	IN_NONE, IN_SOME, IN_THIS, IN_ALL
};

static char *fc__add(const char *, char *, const char *);
static char *fc__conv(int, const char *, char *, const char *);
static char *fc__fmt(const char *, const struct pg_tm *, char *, const char *,
				  enum warn *);
static char *fc__yconv(int, int, bool, bool, char *, char const *);


/*
 * 将时间戳 t 转换为字符串 s，一个调用者分配的大小为 maxsize 的缓冲区，
 * 使用给定的格式模式。
 *
 * 另见 timestamptz_to_str。
 */
size_t pg_strftime(char *fc_s, size_t fc_maxsize, const char *fc_format, const struct pg_tm *fc_t)
{
	char	   *fc_p;
	int			fc_saved_errno = errno;
	enum warn	fc_warn = IN_NONE;

	fc_p = fc__fmt(fc_format, fc_t, fc_s, fc_s + fc_maxsize, &fc_warn);
	if (!fc_p)
	{
		errno = EOVERFLOW;
		return 0;
	}
	if (fc_p == fc_s + fc_maxsize)
	{
		errno = ERANGE;
		return 0;
	}
	*fc_p = '\0';
	errno = fc_saved_errno;
	return fc_p - fc_s;
}

static char * fc__fmt(const char *fc_format, const struct pg_tm *fc_t, char *fc_pt,
	 const char *fc_ptlim, enum warn *fc_warnp)
{
	for (; *fc_format; ++fc_format)
	{
		if (*fc_format == '%')
		{
	label:
			switch (*++fc_format)
			{
				case '\0':
					--fc_format;
					break;
				case 'A':
					fc_pt = fc__add((fc_t->tm_wday < 0 ||
							   fc_t->tm_wday >= DAYSPERWEEK) ?
							  "?" : Locale->weekday[fc_t->tm_wday],
							  fc_pt, fc_ptlim);
					continue;
				case 'a':
					fc_pt = fc__add((fc_t->tm_wday < 0 ||
							   fc_t->tm_wday >= DAYSPERWEEK) ?
							  "?" : Locale->wday[fc_t->tm_wday],
							  fc_pt, fc_ptlim);
					continue;
				case 'B':
					fc_pt = fc__add((fc_t->tm_mon < 0 ||
							   fc_t->tm_mon >= MONSPERYEAR) ?
							  "?" : Locale->month[fc_t->tm_mon],
							  fc_pt, fc_ptlim);
					continue;
				case 'b':
				case 'h':
					fc_pt = fc__add((fc_t->tm_mon < 0 ||
							   fc_t->tm_mon >= MONSPERYEAR) ?
							  "?" : Locale->mon[fc_t->tm_mon],
							  fc_pt, fc_ptlim);
					continue;
				case 'C':

					/*
					 * %C 曾用来做 a... _fmt("%a %b %e %X %Y", t);
					 * ...而现在 POSIX 1003.2 要求完全不同的东西。
					 * (ado, 1993-05-24)
					 */
					fc_pt = fc__yconv(fc_t->tm_year, TM_YEAR_BASE,
								true, false, fc_pt, fc_ptlim);
					continue;
				case 'c':
					{
						enum warn	fc_warn2 = IN_SOME;

						fc_pt = fc__fmt(Locale->c_fmt, fc_t, fc_pt, fc_ptlim, &fc_warn2);
						if (fc_warn2 == IN_ALL)
							fc_warn2 = IN_THIS;
						if (fc_warn2 > *fc_warnp)
							*fc_warnp = fc_warn2;
					}
					continue;
				case 'D':
					fc_pt = fc__fmt("%m/%d/%y", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'd':
					fc_pt = fc__conv(fc_t->tm_mday, "%02d", fc_pt, fc_ptlim);
					continue;
				case 'E':
				case 'O':

					/*
					 * C99 及更高版本的区域设置修饰符。序列 %Ec
					 * %EC %Ex %EX %Ey %EY %Od %oe %OH %OI %Om %OM %OS %Ou %OU
					 * %OV %Ow %OW %Oy 应该提供替代表示。
					 */
					goto label;
				case 'e':
					fc_pt = fc__conv(fc_t->tm_mday, "%2d", fc_pt, fc_ptlim);
					continue;
				case 'F':
					fc_pt = fc__fmt("%Y-%m-%d", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'H':
					fc_pt = fc__conv(fc_t->tm_hour, "%02d", fc_pt, fc_ptlim);
					continue;
				case 'I':
					fc_pt = fc__conv((fc_t->tm_hour % 12) ?
							   (fc_t->tm_hour % 12) : 12,
							   "%02d", fc_pt, fc_ptlim);
					continue;
				case 'j':
					fc_pt = fc__conv(fc_t->tm_yday + 1, "%03d", fc_pt, fc_ptlim);
					continue;
				case 'k':

					/*
					 * 这曾是... _conv(t->tm_hour % 12 ? t->tm_hour %
					 * 12 : 12, 2, ' '); ...并已更改为以下内容
					 * 以匹配 SunOS 4.1.1 和 Arnold Robbins 的 strftime
					 * 版本 3.0。即 "%k" 和 "%l" 已被交换。
					 * (ado, 1993-05-24)
					 */
					fc_pt = fc__conv(fc_t->tm_hour, "%2d", fc_pt, fc_ptlim);
					continue;
#ifdef KITCHEN_SINK
				case 'K':

					/*
					 * 到现在为止，仍然无人认领！
					 */
					fc_pt = _add("kitchen sink", fc_pt, fc_ptlim);
					continue;
#endif							/* 定义 KITCHEN_SINK */
				case 'l':

					/*
					 * 这曾是... _conv(t->tm_hour, 2, ' '); ...并
					 * 已更改为以下内容以匹配 SunOS 4.1.1 和
					 * Arnold Robbins 的 strftime 版本 3.0。即 "%k" 和
					 * "%l" 已被交换。 (ado, 1993-05-24)
					 */
					fc_pt = fc__conv((fc_t->tm_hour % 12) ?
							   (fc_t->tm_hour % 12) : 12,
							   "%2d", fc_pt, fc_ptlim);
					continue;
				case 'M':
					fc_pt = fc__conv(fc_t->tm_min, "%02d", fc_pt, fc_ptlim);
					continue;
				case 'm':
					fc_pt = fc__conv(fc_t->tm_mon + 1, "%02d", fc_pt, fc_ptlim);
					continue;
				case 'n':
					fc_pt = fc__add("\n", fc_pt, fc_ptlim);
					continue;
				case 'p':
					fc_pt = fc__add((fc_t->tm_hour >= (HOURSPERDAY / 2)) ?
							  Locale->pm :
							  Locale->am,
							  fc_pt, fc_ptlim);
					continue;
				case 'R':
					fc_pt = fc__fmt("%H:%M", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'r':
					fc_pt = fc__fmt("%I:%M:%S %p", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'S':
					fc_pt = fc__conv(fc_t->tm_sec, "%02d", fc_pt, fc_ptlim);
					continue;
				case 'T':
					fc_pt = fc__fmt("%H:%M:%S", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 't':
					fc_pt = fc__add("\t", fc_pt, fc_ptlim);
					continue;
				case 'U':
					fc_pt = fc__conv((fc_t->tm_yday + DAYSPERWEEK -
								fc_t->tm_wday) / DAYSPERWEEK,
							   "%02d", fc_pt, fc_ptlim);
					continue;
				case 'u':

					/*
					 * 来自 Arnold Robbins 的 strftime 版本 3.0：“ISO 8601：
					 * 工作日作为十进制数字 [1 (星期一) - 7]” (ado,
					 * 1993-05-24)
					 */
					fc_pt = fc__conv((fc_t->tm_wday == 0) ?
							   DAYSPERWEEK : fc_t->tm_wday,
							   "%d", fc_pt, fc_ptlim);
					continue;
				case 'V':		/* ISO 8601 周数 */
				case 'G':		/* ISO 8601 年（四位数字） */
				case 'g':		/* ISO 8601 年（两位数字） */
/*
 * 来自阿诺德·罗宾斯的strftime版本3.0：“年度的周数
 * （第一周的第一个星期一为第一周第一天）作为一个十进制数字
 * （01-53）。”
 * （ado, 1993-05-24）
 *
 * 来自<https://www.cl.cam.ac.uk/~mgk25/iso-time.html>的马克斯·库恩：
 * “年度的第01周根据定义是该年中包含星期四的第一周，
 * 这相当于包含一月第四天的那一周。换句话说，新年的第一周
 * 是在新年中大多数天数的那一周。第01周也可能包含来自前一年的
 * 天数，而新年的第01周之前的那一周是前一年的最后一周
 * （52或53），即使它包含新年的天数。一周从星期一（第1天）
 * 开始，到星期天（第7天）结束。例如，1997年的第一周
 * 从1996-12-30到1997-01-05……”
 * （ado, 1996-01-02）
 */
					{
						int			fc_year;
						int			fc_base;
						int			fc_yday;
						int			fc_wday;
						int			fc_w;

						fc_year = fc_t->tm_year;
						fc_base = TM_YEAR_BASE;
						fc_yday = fc_t->tm_yday;
						fc_wday = fc_t->tm_wday;
						for (;;)
						{
							int			fc_len;
							int			fc_bot;
							int			fc_top;

							fc_len = isleap_sum(fc_year, fc_base) ?
								DAYSPERLYEAR :
								DAYSPERNYEAR;

							/*
							 * ISO年开始于哪一天（-3 ... 3）？
							 */
							fc_bot = ((fc_yday + 11 - fc_wday) %
								   DAYSPERWEEK) - 3;

							/*
							 * 下一个ISO年开始于哪一天？
							 */
							fc_top = fc_bot -
								(fc_len % DAYSPERWEEK);
							if (fc_top < -3)
								fc_top += DAYSPERWEEK;
							fc_top += fc_len;
							if (fc_yday >= fc_top)
							{
								++fc_base;
								fc_w = 1;
								break;
							}
							if (fc_yday >= fc_bot)
							{
								fc_w = 1 + ((fc_yday - fc_bot) /
										 DAYSPERWEEK);
								break;
							}
							--fc_base;
							fc_yday += isleap_sum(fc_year, fc_base) ?
								DAYSPERLYEAR :
								DAYSPERNYEAR;
						}
						if (*fc_format == 'V')
							fc_pt = fc__conv(fc_w, "%02d",
									   fc_pt, fc_ptlim);
						else if (*fc_format == 'g')
						{
							*fc_warnp = IN_ALL;
							fc_pt = fc__yconv(fc_year, fc_base,
										false, true,
										fc_pt, fc_ptlim);
						}
						else
							fc_pt = fc__yconv(fc_year, fc_base,
										true, true,
										fc_pt, fc_ptlim);
					}
					continue;
				case 'v':

					/*
					 * 来自阿诺德·罗宾斯的strftime版本3.0：“日期为
					 * dd-bbb-YYYY”（ado, 1993-05-24）
					 */
					fc_pt = fc__fmt("%e-%b-%Y", fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'W':
					fc_pt = fc__conv((fc_t->tm_yday + DAYSPERWEEK -
								(fc_t->tm_wday ?
								 (fc_t->tm_wday - 1) :
								 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
							   "%02d", fc_pt, fc_ptlim);
					continue;
				case 'w':
					fc_pt = fc__conv(fc_t->tm_wday, "%d", fc_pt, fc_ptlim);
					continue;
				case 'X':
					fc_pt = fc__fmt(Locale->X_fmt, fc_t, fc_pt, fc_ptlim, fc_warnp);
					continue;
				case 'x':
					{
						enum warn	fc_warn2 = IN_SOME;

						fc_pt = fc__fmt(Locale->x_fmt, fc_t, fc_pt, fc_ptlim, &fc_warn2);
						if (fc_warn2 == IN_ALL)
							fc_warn2 = IN_THIS;
						if (fc_warn2 > *fc_warnp)
							*fc_warnp = fc_warn2;
					}
					continue;
				case 'y':
					*fc_warnp = IN_ALL;
					fc_pt = fc__yconv(fc_t->tm_year, TM_YEAR_BASE,
								false, true,
								fc_pt, fc_ptlim);
					continue;
				case 'Y':
					fc_pt = fc__yconv(fc_t->tm_year, TM_YEAR_BASE,
								true, true,
								fc_pt, fc_ptlim);
					continue;
				case 'Z':
					if (fc_t->tm_zone != NULL)
						fc_pt = fc__add(fc_t->tm_zone, fc_pt, fc_ptlim);

					/*
					 * C99及以后版本说明，如果时区缩写不可确定，
					 * 则 %Z 必须被替换为空字符串。
					 */
					continue;
				case 'z':
					{
						long		fc_diff;
						char const *fc_sign;
						bool		fc_negative;

						if (fc_t->tm_isdst < 0)
							continue;
						fc_diff = fc_t->tm_gmtoff;
						fc_negative = fc_diff < 0;
						if (fc_diff == 0)
						{
							if (fc_t->tm_zone != NULL)
								fc_negative = fc_t->tm_zone[0] == '-';
						}
						if (fc_negative)
						{
							fc_sign = "-";
							fc_diff = -fc_diff;
						}
						else
							fc_sign = "+";
						fc_pt = fc__add(fc_sign, fc_pt, fc_ptlim);
						fc_diff /= SECSPERMIN;
						fc_diff = (fc_diff / MINSPERHOUR) * 100 +
							(fc_diff % MINSPERHOUR);
						fc_pt = fc__conv(fc_diff, "%04d", fc_pt, fc_ptlim);
					}
					continue;
				case '+':
					fc_pt = fc__fmt(Locale->date_fmt, fc_t, fc_pt, fc_ptlim,
							  fc_warnp);
					continue;
				case '%':

					/*
					 * X311J/88-090（4.12.3.5）：如果转换字符未定义，
					 * 则行为未定义。像printf(3)一样打印出字符本身。
					 */
				default:
					break;
			}
		}
		if (fc_pt == fc_ptlim)
			break;
		*fc_pt++ = *fc_format;
	}
	return fc_pt;
}

static char * fc__conv(int fc_n, const char *fc_format, char *fc_pt, const char *fc_ptlim)
{
	char		fc_buf[INT_STRLEN_MAXIMUM(int) + 1];

	sprintf(fc_buf, fc_format, fc_n);
	return fc__add(fc_buf, fc_pt, fc_ptlim);
}

static char * fc__add(const char *fc_str, char *fc_pt, const char *fc_ptlim)
{
	while (fc_pt < fc_ptlim && (*fc_pt = *fc_str++) != '\0')
		++fc_pt;
	return fc_pt;
}

/*
 * POSIX和C标准对于当年份为负或超过9999时，%C和%y的作用
 * 并不清楚或不一致。使用以下约定：%C和%y连接后的输出
 * 与%Y相同，并且%Y至少包含4个字节，如果需要可以更多。
 */

static char * fc__yconv(int fc_a, int fc_b, bool fc_convert_top, bool fc_convert_yy,
	   char *fc_pt, const char *fc_ptlim)
{
	int			fc_lead;
	int			fc_trail;

#define DIVISOR	100
	fc_trail = fc_a % DIVISOR + fc_b % DIVISOR;
	fc_lead = fc_a / DIVISOR + fc_b / DIVISOR + fc_trail / DIVISOR;
	fc_trail %= DIVISOR;
	if (fc_trail < 0 && fc_lead > 0)
	{
		fc_trail += DIVISOR;
		--fc_lead;
	}
	else if (fc_lead < 0 && fc_trail > 0)
	{
		fc_trail -= DIVISOR;
		++fc_lead;
	}
	if (fc_convert_top)
	{
		if (fc_lead == 0 && fc_trail < 0)
			fc_pt = fc__add("-0", fc_pt, fc_ptlim);
		else
			fc_pt = fc__conv(fc_lead, "%02d", fc_pt, fc_ptlim);
	}
	if (fc_convert_yy)
		fc_pt = fc__conv(((fc_trail < 0) ? -fc_trail : fc_trail), "%02d", fc_pt, fc_ptlim);
	return fc_pt;
}
